package com.atguigu.tingshu.album.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.RandomUtil;
import com.atguigu.tingshu.album.mapper.*;
import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.common.cache.TingqCache;
import com.atguigu.tingshu.common.constant.KafkaConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.service.KafkaService;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.common.util.MongoUtil;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.AlbumStat;
import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.query.album.AlbumInfoQuery;
import com.atguigu.tingshu.vo.album.AlbumAttributeValueVo;
import com.atguigu.tingshu.vo.album.AlbumInfoVo;
import com.atguigu.tingshu.vo.album.AlbumListVo;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {

	@Autowired
	private AlbumInfoMapper albumInfoMapper;

    @Autowired
    private AlbumAttributeValueMapper albumAttributeValueMapper;

    @Autowired
    private AlbumStatMapper albumStatMapper;

    @Autowired
    private TrackInfoMapper trackInfoMapper;

    @Autowired
    private KafkaService kafkaService;
    @Autowired
    private BaseCategoryViewMapper baseCategoryViewMapper;

    @Autowired
    private RedisTemplate  redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    //忘记加事务:
    //默认出现RuntimeException 才会回滚
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveAlbumInfo(AlbumInfoVo albumInfoVo) {
        Long userId = AuthContextHolder.getUserId();
        //操作三张表 1. album_info  专辑信息
        //1.1 操作albuminfo转换
        AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);
        albumInfo.setUserId(userId);
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);
        if (!SystemConstant.ALBUM_PAY_TYPE_FREE.equals(albumInfo.getPayType())){
            albumInfo.setTracksForFree(5);
        }
        albumInfoMapper.insert(albumInfo);

        //2. album_attribute_value 专辑信息关联表
        Long albumInfoId = albumInfo.getId();
        List<AlbumAttributeValueVo> attributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();

        //如果没有选关联表 就会NPE了!!
        if (CollectionUtil.isNotEmpty(attributeValueVoList)){
            //转换信息:
            attributeValueVoList.forEach(albumAttributeValuevo -> {
                AlbumAttributeValue albumAttributeValue = BeanUtil.copyProperties(albumAttributeValuevo, AlbumAttributeValue.class);
                albumAttributeValue.setAlbumId(albumInfoId);
                albumAttributeValueMapper.insert(albumAttributeValue);
            });
        }


        //3. album_stat 统计信息表
        //播放量
        this.saveAlbumStat(albumInfoId, SystemConstant.ALBUM_STAT_PLAY);
        //订阅
        this.saveAlbumStat(albumInfoId, SystemConstant.ALBUM_STAT_SUBSCRIBE);
        // 购买
        this.saveAlbumStat(albumInfoId, SystemConstant.ALBUM_STAT_BROWSE);
        //评论数
        this.saveAlbumStat(albumInfoId, SystemConstant.ALBUM_STAT_COMMENT);

        //4.发送消息 让专辑上架  => 进入到索引库中es  这里仅仅是通过开放状态 来上架 正常应该审核通过后上架
        if ("1".equals(albumInfo.getIsOpen())){
            kafkaService.sendMessage(KafkaConstant.QUEUE_ALBUM_UPPER,albumInfoId.toString());
        }

    }

    @Override
    public void saveAlbumStat(Long albumId, String statType) {
        AlbumStat albumStat = new AlbumStat();
        albumStat.setAlbumId(albumId);
        albumStat.setStatNum(0);
        albumStat.setStatType(statType);

        //最后忘记插入了:??

        albumStatMapper.insert(albumStat);
    }
    // 涉及多表操作一定要加事务!!!!又忘记了
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Page<AlbumListVo> findUserAlbumPage(Page<AlbumListVo> pageList, AlbumInfoQuery albumInfoQuery) {
        albumInfoQuery.setUserId(AuthContextHolder.getUserId());
        return albumInfoMapper.findUserAlbumPage(pageList,albumInfoQuery);
    }

    @Override
    public void removeAlbumInfo(Long id) {
        //删除四张表 1. albuminfo
        albumInfoMapper.deleteById(id);

        //第二张表 2. album_stat

        albumStatMapper.delete(new LambdaQueryWrapper<AlbumStat>().eq(AlbumStat::getAlbumId,id));


        //第三章表  3. albumn_attribute_value  标签信息表

        albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId,id));


        //第四张表 4.专辑下的声音 track_info

        trackInfoMapper.delete(new LambdaQueryWrapper<TrackInfo>().eq(TrackInfo::getAlbumId,id));


        //删除专辑直接下架该专辑
        kafkaService.sendMessage(KafkaConstant.QUEUE_ALBUM_LOWER,id.toString());
    }

    @Override
    public AlbumInfo getAlbumInfoById(Long id) {
//        try {
//            //1.专辑详情优化
//            //1.优先从redis中获取数据
//            String dataKey  = RedisConstant.ALBUM_INFO_PREFIX + id ;
//            //先从redis中查询 如果没有 执行第二部
//            AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(dataKey);
//            //命中缓存直接返回
//            if (albumInfo != null){
//                return albumInfo;
//            }
//            //2.构建分布式锁,防止缓存击穿问题 手写
//            //2.1 构建分布式锁的key value 防止被其他线程释放了本不应该属于自己的锁
//            String lockKey  = RedisConstant.ALBUM_LOCK_SUFFIX + id + RedisConstant.CACHE_LOCK_SUFFIX;
//            //2.2 产生锁的标识
//            String lockVal = IdUtil.fastSimpleUUID();
//            //2.3 尝试获取分布式锁 利用set ex ne 命令实现分布式锁
//            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, RedisConstant.ALBUM_LOCK_EXPIRE_PX2, TimeUnit.SECONDS);
//            //2.4 获取锁成功->执行业务
//            if (flag){
//
//                try {
//                    //3.查询业务方法
//                    albumInfo = this.getAlbumInfoByIdFromDB(id);
//                    if (albumInfo == null){
//                        //3.1 如果数据库为空,将空数据放入到缓存中,设置短暂时间为:10分钟
//                        redisTemplate.opsForValue().set(dataKey,albumInfo,RedisConstant.ALBUM_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
//                        return albumInfo;
//                    }
//                    //3.2 查询数据库如果有值 将数据加入缓存中，设置时间：基础时间(1小时)+随机值
//                    int ttl = RandomUtil.randomInt(500, 3600);
//                    redisTemplate.opsForValue().set(dataKey, albumInfo, RedisConstant.ALBUM_TIMEOUT + ttl, TimeUnit.SECONDS);
//                    return albumInfo;
//                } finally {
//                //释放锁: 为了让释放锁具有原子性 使用lua脚本
//                    String text = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
//                            "then\n" +
//                            "    return redis.call(\"del\",KEYS[1])\n" +
//                            "else\n" +
//                            "    return 0\n" +
//                            "end";
//                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
//                    redisScript.setScriptText(text);
//                    redisScript.setResultType(Long.class);
//                    redisTemplate.execute(redisScript, Arrays.asList(lockKey),lockVal);
//                }
//            }else{
//                //2.5 获取锁失败-自旋
//                Thread.sleep(200);
//                return this.getAlbumInfoById(id);
//            }
//        } catch (Exception e) {
//            log.error("【专辑服务】获取专辑异常：{}", e);
//            //兜底处理，查询数据库返回业务数据
//            return this.getAlbumInfoByIdFromDB(id);
//        }
        //使用Redission 分布式锁
        //构建数据key
        try {
            String dataKey  = RedisConstant.ALBUM_INFO_PREFIX + id;
            //1.先查询redis
            AlbumInfo albumInfo =(AlbumInfo) redisTemplate.opsForValue().get(dataKey);
            if (albumInfo != null){
                //如果没有命中缓存 构建分布式锁
                return albumInfo;
            }
            String lockKey = RedisConstant.ALBUM_LOCK_SUFFIX + id + RedisConstant.CACHE_LOCK_SUFFIX;
            //不用构建lockValue 因为: redission 准备好了
            //创建分布式锁
            //获取锁 阻塞线程 直到获取锁为止
            RLock lock = redissonClient.getLock(lockKey);

            try {
                //执行业务逻辑
                //从缓村中查数据
                albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(dataKey);
                if (albumInfo != null){
                    return albumInfo;
                }
                //不存在冲数据 库中查
                albumInfo = this.getAlbumInfoByIdFromDB(id);
                if (albumInfo == null){
              //3.1 如果数据库为空,将空数据放入到缓存中,设置短暂时间为:10分钟
                redisTemplate.opsForValue().set(dataKey,albumInfo,RedisConstant.ALBUM_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                return albumInfo;
         }
                //3.2 查询数据库如果有值 将数据加入缓存中，设置时间：基础时间(1小时)+随机值
                int ttl = RandomUtil.randomInt(500, 3600);
                redisTemplate.opsForValue().set(dataKey, albumInfo, RedisConstant.ALBUM_TIMEOUT + ttl, TimeUnit.SECONDS);
                return albumInfo;
            } finally {
                //不要忘记释放锁
                 lock.unlock();
            }
        } catch (Exception e) {
            log.error("[专辑详情服务调用失败]:{}",e);
            //兜底方法:
            return  this.getAlbumInfoByIdFromDB(id);
        }

    }

    @TingqCache(prefix = RedisConstant.ALBUM_INFO_PREFIX)
    @Override
    public AlbumInfo getAlbumInfoByIdFromDB(Long id) {
        //根据专辑id 查询专辑的基本信息 album_info
        AlbumInfo albumInfo = albumInfoMapper.selectById(id);
        //根据专辑id 查询到它的专辑属性值列表
        List<AlbumAttributeValue> albumAttributeValues = albumAttributeValueMapper
                .selectList(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, id));
        albumInfo.setAlbumAttributeValueVoList(albumAttributeValues);
        return  albumInfo;
    }

    @Override
    public void updateAlbumInfo(Long id, AlbumInfoVo albumInfoVo) {
        //更新本地数据 也就是专辑基本信息 由于是albuminfovo 所以类型转换
        AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);
        albumInfo.setId(id);
        albumInfoMapper.updateById(albumInfo);

        //有可能跟新 新的属性信息 先删除 在添加

        albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId,id));

        List<AlbumAttributeValueVo> attributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();

        if (CollectionUtil.isNotEmpty(attributeValueVoList)){

            attributeValueVoList.forEach(albumAttributeValueVo ->{
                AlbumAttributeValue albumAttributeValue = BeanUtil.copyProperties(albumAttributeValueVo, AlbumAttributeValue.class);
                //设置 里面的id
                albumAttributeValue.setAlbumId(id);
                albumAttributeValueMapper.insert(albumAttributeValue);
            });
        }

        //更新上下架情况
        //4.发送消息 让专辑上架  => 进入到索引库中es  这里仅仅是通过开放状态 来上架 正常应该审核通过后上架
        if ("1".equals(albumInfo.getIsOpen())){

            kafkaService.sendMessage(KafkaConstant.QUEUE_ALBUM_UPPER,id.toString());
        }
        //下架!
        if ("0".equals(albumInfo.getIsOpen())){
            kafkaService.sendMessage(KafkaConstant.QUEUE_ALBUM_LOWER,id.toString());
        }


    }

    @Override
    public List<AlbumInfo> findUserAllAlbumList() {

        //获得当前登陆的的用户id
        Long userId = AuthContextHolder.getUserId();

        LambdaQueryWrapper<AlbumInfo> wrapper = new LambdaQueryWrapper<>();
        //根据id查询
        wrapper.eq(AlbumInfo::getUserId,userId)
                .select(AlbumInfo::getId,AlbumInfo::getAlbumTitle)
                //根据时间排序
                .orderByDesc(AlbumInfo::getCreateTime)
                .last("limit 20");
        return albumInfoMapper.selectList(wrapper);
    }
    @TingqCache(prefix = "albumStat:")
    @Override
    public AlbumStatVo getAlbumStatById(Long albumId) {

        return baseMapper.getAlbumStatById(albumId);
    }

    @Autowired
    private MongoTemplate mongoTemplate;
    /**
     * 用户是否订阅
     * @param albumId
     * @return
     */
    @Override
    public boolean isSubscribe(Long albumId)
    {
        Long userId = AuthContextHolder.getUserId();
        Query query = Query.query(Criteria.where("userId").is(userId).and("albumId").is(albumId));
        long count = mongoTemplate.count(query, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_SUBSCRIBE, userId));
        if(count > 0) return true;
        return false;

    }
}
